package io.github.uicdb.et.net;

import com.mojang.serialization.Codec;
import io.github.uicdb.et.ModElements;
import io.github.uicdb.et.element.BasicElement;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.core.Holder;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.function.Function;

@Getter
@RequiredArgsConstructor
public class ElementAttachmentData {
    public static final StreamCodec<RegistryFriendlyByteBuf, ElementAttachmentData> STREAM_CODEC = new StreamCodec<>() {
        @Override
        public void encode(RegistryFriendlyByteBuf o, ElementAttachmentData data) {
            var map = data.attachedData;
            o.writeVarInt(map.size());
            map.forEach((k, v) -> {
                o.writeVarInt(k.getIndex());
                AttachedData.STREAM_CODEC.encode(o, v);
            });
        }

        @Override
        public @NotNull ElementAttachmentData decode(RegistryFriendlyByteBuf o) {
            EnumMap<ModElements, AttachedData> attachedData = new EnumMap<>(ModElements.class);
            for (int i = 0; i < o.readVarInt(); i++) {
                int index = o.readVarInt();
                ModElements elements = ModElements.values()[index];
                AttachedData data = AttachedData.STREAM_CODEC.decode(o);
                attachedData.put(elements, data);
            }
            return new ElementAttachmentData(attachedData);
        }
    };
    final EnumMap<ModElements, AttachedData> attachedData;
    public static final Codec<ElementAttachmentData> CODEC = Codec.unboundedMap(
                    ModElements.CODEC, AttachedData.CODEC
            ).xmap(map -> map.isEmpty() ? new EnumMap<>(ModElements.class) : new EnumMap<>(map), Function.identity())
            .xmap(ElementAttachmentData::new, ElementAttachmentData::getAttachedData);

    public ElementAttachmentData copy() {
        var map = new EnumMap<ModElements, AttachedData>(ModElements.class);
        for (ModElements keys : attachedData.keySet()) {
            map.put(keys, attachedData.get(keys).copy());
        }
        return new ElementAttachmentData(map);
    }

    public ElementAttachmentData() {
        attachedData = new EnumMap<>(ModElements.class);
    }

    public void set(ModElements elements, AttachedData attachedData) {
        if (attachedData == null || attachedData.getRemainTicks() <= 0) this.attachedData.remove(elements);
        else
            this.attachedData.put(elements, attachedData);
    }
    public ElementAttachmentData set(ModElements elements, int amount,int tick,@Nullable LivingEntity attacker) {
        if(amount <= 0|| tick<=0) this.attachedData.remove(elements);
        if(attachedData.containsKey(elements))
            attachedData.get(elements).set(amount,tick,attacker);
        else attachedData.put(elements,new AttachedData(amount,tick,amount));
        return this;
    }
    public void setAmount(ModElements elements, int amount) {
        var get = get(elements);
        if(get != null) get.setAmount(amount);
    }
    public void setTick(ModElements elements, int tick) {
        var get = get(elements);
        if(get != null) get.setRemainTicks(tick);
    }
    public void setAttacker(ModElements elements, LivingEntity attacker) {
        var get = get(elements);
        if(get != null) get.setAttacker(attacker==null?-1:attacker.getId());
    }
    @Nullable
    public LivingEntity getAttacker(ModElements elements, Level level) {
        var get = get(elements);
        if(get != null)
            return (LivingEntity) level.getEntity(get.getAttacker());
        return null;
    }
    public int getAmount(ModElements elements) {
        var get = get(elements);
        if(get != null) return get.getAmount();
        return -1;
    }
    public int getTick(ModElements elements) {
        var get = get(elements);
        if(get != null) return get.getRemainTicks();
        return -1;
    }
    public AttachedData get(ModElements elements) {
        return attachedData.get(elements);
    }

    public boolean has(Holder<BasicElement> elementHolder) {
        return attachedData.containsKey(ModElements.byHolder(elementHolder));
    }
    public boolean has(ModElements elements) {
        return attachedData.containsKey(elements);
    }
    public ElementAttachmentData tickDown(){
       var iter= attachedData.entrySet().iterator();
       while(iter.hasNext()){
           var entry = iter.next();
           AttachedData data = entry.getValue();
           data.setRemainTicks(data.getRemainTicks() - 1);
           if(data.getRemainTicks() <= 0) iter.remove();
       }
        return this;
    }
}
